; 程序起始地址，由BIOS决定的
org 0x7c00
BaseOfStack equ 0x7c00
; Loader程序的加载地址为0x1000:0x00(0x1000 << 4 + 0x00 = 0x10000)
BaseOfLoader equ 0x1000
OffsetOfLoader equ 0x00
SectorNumOfRootDirStart equ 30088    ; 根目录起始扇区号：保留扇区数 +  隐藏扇区数+ 一个FAT的扇区数 × FAT表个数 + (起始簇号-2) x 每簇的扇区数 [BPB_RsvdSecCnt] + [BPB_HiddSec] + [BPB_FATSz32] * 2 + ([BPB_RootClus] - 2) * [BPB_SecPerClus]
SectorNumOfFAT1Start    equ 102    ; FAT1表起始扇区号，紧跟保留扇区后 [BPB_RsvdSecCnt] + [BPB_HiddSec]
SectorBalance       equ     30024    ; 因为数据区对应的有效簇号是2，为了正确计算出FAT表项对应的数据区起始扇区号，必须将FAT表项值减2，这里先将根目录起始扇区号减2*BPB_SecPerClus [SectorNumOfRootDirStart] - 2 *  [BPB_SecPerClus]
    ; FAT32文件系统引导扇区结构
    jmp short Label_Start       ; 跳转指令，后接一个空指令，3字节
    nop
    BS_OEMName              db                  'MINEboot'      ; 生产厂商，8字节
    BPB_BytesPerSec         dw                  512             ; 每扇区字节数，2字节
    BPB_SecPerClus          db                  0x20            ; 每簇扇区数，文件系统管理单位，1字节
    BPB_RsvdSecCnt          dw                  0x26            ; 保留扇区数，第一个为引导扇区，2字节
    BPB_NumFATs             db                  2               ; FAT表的份数，1字节
    BPB_RootEntCnt          dw                  0               ; 根目录可容纳的目录项数，2字节
    BPB_TotSec16            dw                  0               ; 总扇区数，2字节
    BPB_Media               db                  0xf8            ; 介质描述符，1字节,硬盘为0xf8，软盘为0xf0
    BPB_FATSz16             dw                  0               ; 每FAT表占用扇区数，2字节
    BPB_SecPerTrk           dw                  0x3f            ; 每磁道扇区数，2字节
    BPB_NumHeads            dw                  0xff            ; 磁头数，2字节
    BPB_HiddSec             dd                  0x40            ; 隐藏扇区数，4字节
    BPB_TotSec32            dd                  0x03a97fc0      ; 如果BPB_TotSec16为0，则由这个值记录扇区总数，4字节，C/H/S = 73/16/63
    BPB_FATSz32             dd                  0x3a91          ; 每个FAT表占用扇区数，4字节
    BPB_Flags               dw                  0               
    BPB_FSVer               dw                  0
    BPB_RootClus            dd                  2               ; 根目录的第一个簇的簇号，4字节
    BPB_FSIfo               dw                  1               ; 保留区中FAT32卷FSINFO结构所占的扇区数，通常为1，2字节
    BPB_BkBootSec           dw                  6               ; 如果不为0，表示在保留区中引导记录的备数据所占的扇区数，通常为6，2字节
    BPB_Reserved times 12   db                  0               ; 预留
    BS_DrvNum               db                  0x80            ; int 13h的驱动器号，1字节
    BS_Reserved1            db                  0               ; 未使用，1字节
    BS_BootSig              db                  29h             ; 扩展引导标记，1字节
    BS_VolID                dd                  0xc65bb56d      ; 卷序列号，4字节
    BS_VolLab               db                  'boot loader'   ; 卷标，11字节
    BS_FileSysType          db                  'FAT32   '      ; 文件系统类型，8字节

Label_Start:
    ; 设置ds,es,ss,sp等寄存器初始值为0x7c00
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, BaseOfStack

; =========== clear screen
    ; INT 10h, AH=06h功能：按指定范围滚动窗口
    ; AL = 滚动的列数，若为0则实现清空屏幕功能
    ; BH = 滚动后空出位置放入的属性
    ; CH = 滚动范围的左上角坐标列号
    ; CL = 滚动范围的左上角坐标行号
    ; DH = 滚动范围的右下角坐标列号
    ; DL = 滚动范围的右下角坐标行号
    ; BH = 颜色属性
    ;       bit0~2: 字体颜色（0：黑，1：蓝，2：绿，3：青，4：红，5：紫，6：棕，7：白）
    ;       bit3: 字体亮度（0：字体正常，1：字体高亮度）
    ;       bit4~6: 背景颜色（0：黑，1：蓝，2：绿，3：青，4：红，5：紫，6：棕，7：白）
    ;       bit7: 字体闪烁（0：不闪烁，1：字体闪烁）
    mov ax, 0600h
    mov bx, 0700h
    mov cx, 0
    mov dx, 0184fh
    int 10h
; =========== set focus
    ; INT 10h, AH=02h功能：设定光标位置
    ; DH = 游标的行数
    ; DL = 游标的列数
    ; BH = 页码
    mov ax, 0200h
    mov bx, 0000h
    mov dx, 0000h
    int 10h
; =========== display on screen : Start Booting......
    ; INT 10h, AH=13h功能：显示一行字符串
    ; AL = 写入模式
    ;       00h: 字符串的属性由BL寄存器提供，而CX寄存器提供字符串长度（以B为单位），显示后光标位置不变
    ;       01h: 同00h，但光标会移动至字符串尾端位置
    ;       02h: 字符串属性由每个字符后面紧跟的字节提供，故CX寄存器提供的字符串长度改成以Word为单位，显示后光标位置不变
    ;       03h: 同02h，但光标会移动至字符串尾端位置
    ; CX = 字符串长度
    ; DH = 游标的坐标行号
    ; DL = 游标的坐标列号
    ; ES:BP = 要显示的字符串的内存地址
    ; BH = 页码
    ; BL = 字符属性
    ;       bit0~2: 字体颜色（0：黑，1：蓝，2：绿，3：青，4：红，5：紫，6：棕，7：白）
    ;       bit3: 字体亮度（0：字体正常，1：字体高亮度）
    ;       bit4~6: 背景颜色（0：黑，1：蓝，2：绿，3：青，4：红，5：紫，6：棕，7：白）
    ;       bit7: 字体闪烁（0：不闪烁，1：字体闪烁）
    mov ax, ds
    mov es, ax
    mov bp, StartBootMessage
    mov ax, 1301h
    mov bx, 000fh
    mov dx, 0000h
    mov cx, 10
    int 10h

; =========== search loader.bin
    mov dword [SectorNo], SectorNumOfRootDirStart    ; 根目录起始扇区号存到SectorNo变量中
Label_Search_In_Root_Dir_Begin:
    mov ax, 00h                         ; ax = 0
    mov es, ax                          ; es = 0
    mov bx, 8000h                       ; bx = 0x8000
    mov eax, [SectorNo]                 ; eax = SectorNo，第一个根目录扇区
    mov cx, 1                           ; cx = 1
    call Func_ReadOneSector             ; 第0个扇区，也就是引导扇区加载到0x8000
    mov si, LoaderFileName              ; si = LoaderFileName的起始地址
    mov di, 8000h                       ; di = 0x8000
    cld                                 ; DF位置0，表示从低地址向高地址读取
    mov dx, 10h                         ; dx = 0x10，记录每个扇区容纳的目录项个数，一个目录项32字节，512/32=16=0x10
Label_Search_For_LoaderBin:
    cmp dx, 0                           ; if (dx == 0)
    jz Label_Goto_Next_Sector_In_Root_Dir    ; 如果dx == 0成立，表示当前扇区搜索完成，跳转Label_Goto_NextSector_In_Root_Dir循环执行下一个扇区的搜索
    dec dx                              ; dx减1
    mov cx, 11                          ; cx = 11，记录文件名长度，总共11字节
Label_Cmp_FileName:
    cmp byte [es:di], 0                 ; if 根目录项第一个字节为0表示搜索结束
    jz Label_No_LoaderBin               ; 没有找到文件
    cmp cx, 0                           ; if (cx == 0)
    jz Label_FileName_Found             ; 如果cx == 0成立，表示找到loader.bin，跳转到Label_FileName_Found
    dec cx                              ; cx减1
    lodsb                               ; 从ds:si读取1个字节数据到al，si加1，依次读取文件名到al
    cmp al, byte [es:di]                ; al中保存文件名，es:di保存从扇区读取的数据
    jz Label_Go_On                      ; 相等跳转到Label_Go_On继续对比
    jmp Label_Different                 ; 不相等跳转到Label_Diferent

Label_Go_On:
    inc di                              ; 前面判断成立，di加1
    jmp Label_Cmp_FileName              ; 跳转到Label_Cmp_FileName循环对比文件名
Label_Different:                        ; 文件名不相同
    and di, 0ffe0h                      ; di = di & 0xffe0，每个目录项32字节，当前目录项没有找到，di & 0xffe0回到目录项的起始地址
    add di, 20h                         ; di = di + 0x20，di指向下一个目录项
    mov si, LoaderFileName              ; si重新指向文件名起始地址
    jmp Label_Search_For_LoaderBin      ; 跳转到Label_Search_For_LoaderBin继续匹配文件名
Label_Goto_Next_Sector_In_Root_Dir:
    add word [SectorNo], 1              ; SectorNo变量加1
    jmp Label_Search_In_Root_Dir_Begin  ; 跳转到Label_Search_In_Root_Dir_Begin开始新的扇区搜索

; =========== display on screen: ERROR:No LOADER Found
Label_No_LoaderBin:     ; 同前面显示StartBootMessage
    mov ax, ds
    mov es, ax
    mov ax, 1301h
    mov bx, 008ch
    mov dx, 0100h
    mov cx, 21
    mov bp, NoLoaderMessage
    int 10h
    jmp $

; =========== found loader.bin name in root director struct
; FAT12根目录项结构
;   名称            偏移                长度                描述
; DIR_Name          0x00                11                  文件名8字节，扩展名3字节
; DIR_Attr          0x0b                1                   文件属性
; 保留              0x0c                10                  保留位
; DIR_WrtTime       0x16                2                   最后一次写入时间
; DIR_WrtDate       0x18                2                   最后一次写入日期
; DIR_FstClus       0x1a                2                   起始簇号
; DIR_FileSize      0x1c                4                   文件大小
; 由于FAT[0]和FAT[1]是保留项，不能用于数据区的簇索引，因此数据区的第一个有效簇号是2，而不是0或1

Label_FileName_Found:                   ; 找到loader.bin文件处理流程
    xor ecx, ecx
    and di, 0ffe0h                      ; di指向匹配到的目录项首地址
    add di, 0x14                        ; di指向文件起始簇号的高两个字节
    mov cx, word [es:di]                ; 读取起始簇号高两个字节
    shl ecx, 16                         ;
    and di, 0ffe0h                      ; di指向匹配到的目录项首地址
    add di, 01ah                        ; di = di + 0x1a，di指向起始簇号低两个字节
    mov cx, word [es:di]                ; 读取起始簇号保存到cx
    push ecx                            ; ecx压栈
    mov eax, 0                          ; eax = 0
    mov al, [BPB_SecPerClus]            ; al = BPB_SecPerClus, cx中保存起始簇号，乘BPB_SecPerClus得到起始逻辑扇区号
    mul ecx                             ; 起始逻辑扇区号保存到eax
    mov ecx, eax                        ; ecx = eax
    add ecx, SectorBalance              ; ecx加上SectorBalance找到对应数据区物理起始扇区号
    mov ax, BaseOfLoader                ; ax保存loader文件保存段地址0x1000
    mov es, ax                          ; 文件保存段地址赋值给es
    mov bx, OffsetOfLoader              ; bx保存loader文件保存的偏移地址，现在文件保存地址为es:bx
    mov eax, ecx                        ; 前面计算的数据区扇区号保存到eax
Label_Go_On_Loading_File:
    ; INT 10h, AH=0Eh功能：在屏幕上显示一个字符
    ;       AL = 待显示的字符
    ;       BL = 前景色
    push eax                             ; ax压栈
    push ebx                             ; bx压栈
    ; 显示一个字符.
    mov ah, 0eh                         ; ah = 0x0e
    mov al, '.'
    mov bl, 0fh
    int 10h
    ; 恢复ax,bx
    pop ebx
    pop eax

    mov ch, 0
    mov cl, [BPB_SecPerClus]            ; cx = 1，读取一个簇
    call Func_ReadOneSector             ; 调用Func_ReadOneSector读取一个扇区
    mov ax, [BPB_BytesPerSec]           ; 获取一个扇区的大小
    mul dword [BPB_SecPerClus]          ; 计算一个簇的大小
    add bx, ax                          ; bx = bx + 簇字节数，bx中地址增加一个簇，读取下一个簇
    pop eax                             ; 从栈中取出Label_FileName_Found中push ecx保存的起始簇号
    call Func_GetFATEntry               ; 调用Func_GetFATEntry获取下一个FAT表
    cmp eax, 0x0fffffff                 ; 如果取得值为0x0fffffff表示结束簇
    jz Label_File_Loaded                ; 跳转到Label_File_Loaded，文件加载完成
    push eax                            ; 否则保存簇号，用于下一次查找下一个FAT表
    add eax, SectorBalance              ; ax = ax + SectorBalance获得对应数据区扇区号, 这里有问题，由于loader文件一般很小，不会跑到这里，而boot.bin快超过512字节了，暂时不改
    jmp Label_Go_On_Loading_File        ; 跳转到Label_Go_On_Loading_File继续读取文件

Label_File_Loaded:
    ;jmp $   ; 死循环
    jmp BaseOfLoader:OffsetOfLoader     ; 跳转到loader程序执行

; =========== read one sector from disk
    ; INT 13h, AH=42h功能：lba寻址方式读取磁盘扇区
    ; CL = 读入的扇区数（必须非0）
    ; EAX = LAB方式扇区编号
    ; DL = 驱动器号（如果操作的是硬盘驱动器，bit7必须被置位）
    ; ES:BX = 数据缓冲区
Func_ReadOneSector:
    ; 输入参数：EAX = 待读取的磁盘起始扇区号
    ;           CX = 读入的扇区数量
    ;           ES:BX = 目标缓冲区起始地址
    push dword 00h                      ; 扇区起始号高4字节
    push dword eax                      ; 扇区起始号
    push word es                        ; 缓存地址段地址
    push word bx                        ; 缓存地址段偏移
    push word cx                        ; 读取扇区个数
    push word 10h                       ; 高字节是预留字节为0，低字节10h表示包结构为16个字节
    mov ax, 4200h                         ; INT 13h，AH=42h功能：磁盘读取,DL=磁盘驱动器号,DS:SI指向硬盘地址包
    mov dl, 0x80
    mov si, sp
    int 13h
    add sp, 10h                         ; 读取完成后要还原栈空间
    ret                                 ; 返回

; =========== get FAT Entry

; 硬盘FAT32文件系统分配图
; 数据区                        ?                               数据区不但可以保存目录项信息还可以保存文件内的数据
; 根目录区                      1164~?                          根目录区只能保存目录项信息，为0表示结束
; FAT2                          598~1163                        备份FAT表
; FAT1                          32~597                          FAT表
; 保留扇区，第一个为引导扇区    0~31

; FAT32表项说明
; 第一个表项0x0FFFFFF8，磁盘标示字，低字节与BPB_Media数值保持一致
; 第二个表项0x0FFFFFFF，第一个簇已经被占用
; 第三个表项0x0FFFFFF8
; 0x00000000: 可用簇
; 0xFFFFFFF7: 坏簇
; 0x0FFFFFFF: 文件的最后一个簇

Func_GetFATEntry:
    ; 输入eax = FAT表项号
    ; 输出eax = FAT表项号
    push es                         ; 保存es
    push ebx                         ; 保存ebx
    push eax                         ; 保存eax
    mov ax, 00                      ; ax = 0
    mov es, ax                      ; es = 0
    pop eax                          ; 恢复ax
    mov ebx, 4
    mul ebx

    xor edx, edx                      ; dx = 0
    mov bx, [BPB_BytesPerSec]       ; bx = 512
    div bx                          ; ax = ax / 512，ax得到扇区号，dx得到扇区偏移地址
    push dx                         ; 保存dx
    mov bx, 8000h                   ; bx = 0x8000
    add eax, SectorNumOfFAT1Start   ; FAT1起始扇区号为1，得到FAT1真正扇区号
    mov cx, 1                       ; cx = 1，读取1个扇区的数据
    call Func_ReadOneSector         ; 调用Func_ReadOneSector读取FAT1的扇区数据

    pop dx                          ; 恢复dx
    add bx, dx                      ; bx加上偏移地址，得到表项FAT地址
    mov eax, dword [es:bx]          ; 读取4字节数据
    pop ebx                         ; 恢复ebx
    pop es                          ; 恢复es
    ret                             ; 返回

; =========== tmp variable
    FatEntryNo dd 0
    SectorNo dd 0

; =========== display messages

    StartBootMessage:   db  "Start Boot"
    NoLoaderMessage:    db  "ERROR:No LOADER Found"
    LoaderFileName:     db  "LOADER  BIN",0

; =========== fill zero until whole sector
    times 510 - ($ - $$)    db 0
    dw 0xaa55
